Repository navigation
MicroHS support - #11392
MicroHS support#11392ulysses4ever wants to merge 6 commits into
Conversation
|
UPD: the issue below is no longer relevant as of Oct 16, 2026 I got as far as solving dependencies: on a freshly I'm getting: I guess I need to better understand how MHS treats |
dcb78df to
1f2da2d
Compare
1f2da2d to
b9bb698
Compare
|
@augustss I hope to get your input here. This work (allowing cabal to drive mhs) uncovered many things some of which need fixing on mhs' side if you are willing to review. The full list is here: https://github.com/ulysses4ever/cabal-mhs-testsuite/blob/main/INVENTORY.md I can start submitting them to the mhs repo if this seems useful to you. Also, I'm happy to discuss them elsewhere if that's preferable. |
|
Let me look at the list. |
eaf6002 to
5bcc71f
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved backend correctness issues can break supported package builds and leave installed-package information stale.
6 open issues
Reject unsupported renamed module reexports · New Inject ghc-compat independently into each component · New Monitor package files and subdirectories for index changes · New Preserve private headers and relative include layout · New Search preprocessing output for generated main-is files · New Include preprocessing output in module search paths · New
What changed in this PR
Adds MicroHs support to Cabal’s compiler backend and cabal-install’s planning and installation workflows.
Changes:
- Implements MHS configuration, package discovery, building, registration, and installation.
- Adapts dependency solving and library installation for MicroHs.
- Adds integration fixtures, documentation, and generated help updates.
| File | Description |
|---|---|
| doc/setup-help/configure.txt | Documents MHS compiler selection. |
| doc/setup-help/build.txt | Lists the MHS program. |
| doc/setup-commands.rst | Documents MHS options. |
| doc/config.rst | Lists MicroHs among known programs. |
| doc/cmd-v2-help/update.txt | Adds MHS selection help. |
| doc/cmd-v2-help/test.txt | Adds MHS selection help. |
| doc/cmd-v2-help/target.txt | Adds MHS selection help. |
| doc/cmd-v2-help/run.txt | Adds MHS selection help. |
| doc/cmd-v2-help/repl.txt | Adds MHS selection help. |
| doc/cmd-v2-help/path.txt | Adds MHS selection help. |
| doc/cmd-v2-help/outdated.txt | Adds MHS selection help. |
| doc/cmd-v2-help/list-bin.txt | Adds MHS selection help. |
| doc/cmd-v2-help/install.txt | Adds MHS selection help. |
| doc/cmd-v2-help/haddock.txt | Adds MHS selection help. |
| doc/cmd-v2-help/gen-bounds.txt | Adds MHS selection help. |
| doc/cmd-v2-help/freeze.txt | Adds MHS selection help. |
| doc/cmd-v2-help/exec.txt | Adds MHS selection help. |
| doc/cmd-v2-help/configure.txt | Adds MHS selection help. |
| doc/cmd-v2-help/build.txt | Adds MHS selection help. |
| doc/cmd-v2-help/bench.txt | Adds MHS selection help. |
| doc/cmd-v1-help/reconfigure.txt | Adds MHS selection help. |
| doc/cmd-v1-help/install.txt | Documents MHS selection and program. |
| doc/cmd-v1-help/configure.txt | Documents MHS selection and program. |
| doc/cmd-v1-help/build.txt | Lists the MHS program. |
| doc/cabal-project-description-file.rst | Lists MHS compiler configuration. |
| doc/cabal-package-description-file.rst | Updates documented compiler flavors. |
| doc/cabal-commands.rst | Explains MHS library installation. |
| changelog.d/pr-11392.md | Records support and limitations. |
| Cabal/src/Distribution/Simple/Setup/Config.hs | Adds the --mhs option. |
| Cabal/src/Distribution/Simple/Register.hs | Dispatches MHS registration operations. |
| Cabal/src/Distribution/Simple/Program/Builtin.hs | Defines the MHS program. |
| Cabal/src/Distribution/Simple/Program.hs | Exports the MHS program. |
| Cabal/src/Distribution/Simple/MHS.hs | Implements the MicroHs backend. |
| Cabal/src/Distribution/Simple/Install.hs | Dispatches MHS installation. |
| Cabal/src/Distribution/Simple/Configure.hs | Integrates MHS configuration and discovery. |
| Cabal/src/Distribution/Simple/Compiler.hs | Generalizes reexport capability detection. |
| Cabal/src/Distribution/Simple/Build.hs | Dispatches MHS builds. |
| Cabal/Cabal.cabal | Exposes the backend module. |
| cabal-testsuite/PackageTests/MHS/Macros/src/Versions.hs | Exercises version macros. |
| cabal-testsuite/PackageTests/MHS/Macros/mhs-macros.cabal | Defines the macro fixture. |
| cabal-testsuite/PackageTests/MHS/Macros/cabal.test.hs | Checks macro output. |
| cabal-testsuite/PackageTests/MHS/Macros/cabal.project | Selects MHS for macro tests. |
| cabal-testsuite/PackageTests/MHS/Macros/app/Main.hs | Exercises header and library macros. |
| cabal-testsuite/PackageTests/MHS/InstallLib/src/Triple.hs | Provides an FFI library fixture. |
| cabal-testsuite/PackageTests/MHS/InstallLib/prog/Prog.hs | Consumes the installed library. |
| cabal-testsuite/PackageTests/MHS/InstallLib/mhs-installlib.cabal | Defines the installation fixture. |
| cabal-testsuite/PackageTests/MHS/InstallLib/include/triple.h | Supplies the installed C header. |
| cabal-testsuite/PackageTests/MHS/InstallLib/cbits/triple.c | Implements the C fixture. |
| cabal-testsuite/PackageTests/MHS/InstallLib/cabal.test.hs | Tests installation and standalone consumption. |
| cabal-testsuite/PackageTests/MHS/InstallLib/cabal.project | Selects MHS for installation tests. |
| cabal-testsuite/PackageTests/MHS/CSources/src/Twice.hs | Exercises in-place library FFI. |
| cabal-testsuite/PackageTests/MHS/CSources/repo/mhs-cdep-1.0/src/Add.hs | Exercises dependency C API imports. |
| cabal-testsuite/PackageTests/MHS/CSources/repo/mhs-cdep-1.0/mhs-cdep.cabal | Defines the repository dependency. |
| cabal-testsuite/PackageTests/MHS/CSources/repo/mhs-cdep-1.0/include/add.h | Supplies dependency declarations and macro. |
| cabal-testsuite/PackageTests/MHS/CSources/repo/mhs-cdep-1.0/cbits/add.c | Implements dependency C functionality. |
| cabal-testsuite/PackageTests/MHS/CSources/mhs-csources.cabal | Defines C compilation fixtures. |
| cabal-testsuite/PackageTests/MHS/CSources/include/twice.h | Supplies the library header. |
| cabal-testsuite/PackageTests/MHS/CSources/cbits/twice.c | Implements library C functionality. |
| cabal-testsuite/PackageTests/MHS/CSources/cabal.test.hs | Checks C linking and compiler options. |
| cabal-testsuite/PackageTests/MHS/CSources/cabal.project | Selects MHS for C tests. |
| cabal-testsuite/PackageTests/MHS/CSources/app/offset.h | Declares executable C functionality. |
| cabal-testsuite/PackageTests/MHS/CSources/app/offset.c | Exercises a C compiler definition. |
| cabal-testsuite/PackageTests/MHS/CSources/app/Main.hs | Runs the C integration checks. |
| cabal-testsuite/PackageTests/MHS/Build/src/Greeting/Internal.hs | Exercises hidden modules and bytestring. |
| cabal-testsuite/PackageTests/MHS/Build/src/Greeting.hs | Exposes the library greeting. |
| cabal-testsuite/PackageTests/MHS/Build/mhs-test.cabal | Defines the build fixture. |
| cabal-testsuite/PackageTests/MHS/Build/cabal.test.hs | Checks building and running. |
| cabal-testsuite/PackageTests/MHS/Build/cabal.project | Selects MHS for build tests. |
| cabal-testsuite/PackageTests/MHS/Build/app/Main.hs | Consumes the fixture library. |
| cabal-install/src/Distribution/Client/ProjectPlanning.hs | Applies compiler-specific dependency rewriting. |
| cabal-install/src/Distribution/Client/Dependency.hs | Rewrites MHS source-package descriptions. |
| cabal-install/src/Distribution/Client/CmdPath.hs | Resolves the MHS compiler program. |
| cabal-install/src/Distribution/Client/CmdInstall.hs | Routes MHS library installation. |
Review effort: Balanced
5bcc71f to
91138c9
Compare
The six commits of haskell/cabal#11392 with the review of Copilot addressed, on master aeac769eb, each replayed the same way: 3, 39, 58, 88, 91 and 91 of 91 packages build and run, as before the review. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved package metadata, header lookup, and installation issues can break dependency resolution and linking.
7 open issues
Rewrite package-qualified reexports for forked dependencies · New Apply compatibility mapping to registered base packages · New Preserve linker metadata for source-free libraries · New Add generated headers to C include paths · New Resolve extra-lib-dirs relative to the defining package · New Avoid rescanning all package databases for every component · New Remove obsolete C sources during library reinstall · New
6 resolved since last review
Include preprocessing output in module search paths Search preprocessing output for generated main-is files Preserve private headers and relative include layout Monitor package files and subdirectories for index changes Inject ghc-compat independently into each component Reject unsupported renamed module reexports
Review effort: Balanced
91138c9 to
7dd5b19
Compare
The six commits of haskell/cabal#11392 with the second review of Copilot addressed, on master aeac769eb, each replayed the same way: 3, 39, 58, 88, 91 and 91 of 91 packages build and run, as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
There was a problem hiding this comment.
🟡 Changes recommended
Registered-base package lookup and Windows installation-test isolation remain unresolved.
2 open issues
7 resolved since last review
Apply compatibility mapping to registered base packages Rewrite package-qualified reexports for forked dependencies Remove obsolete C sources during library reinstall Avoid rescanning all package databases for every component Resolve extra-lib-dirs relative to the defining package Add generated headers to C include paths Preserve linker metadata for source-free libraries
Review effort: Balanced
|
@Mikolaj there’s an opportunity for refactoring but it would be easier to merge this pr and then refactor than to refactor and adapt this pr. Mostly because a lot of effort went into documenting the feature parity with mcabal in the PR description. But also rebasing is tiring (and the refactor shouldn’t be a part of this PR as it’s a completely orthogonal concern). The details of the refactoring (which Im happy to drive after merging this) are below as written up by Fable 5.1. Agreed on the diagnosis. Outside The PR follows the existing structure rather than inventing one. There are about fifty such dispatch sites on What I would replace the mechanism with is a record of functions: data Backend = Backend
{ backendConfigure :: …
, backendGetInstalledPackages :: …
, backendBuildLib :: …
, backendBuildExe :: …
, backendInstallLib :: …
, backendInstallExe :: …
, backendRegisterPackage :: …
, backendCreatePackageDB :: …
, backendRepl :: Maybe … -- and the other capabilities most backends lack
}
backend :: CompilerFlavor -> Backend -- one total case, no catch-allThe As for using the type as data: I would do this as a separate PR after this one. The refactor rewrites the GHC paths, which carry nearly all users, and reviewing that together with a new compiler mixes two risks. A second real backend is also what makes the interface worth defining and what tests it. I am happy to write that follow-up once this lands. |
|
Sounds good. Agreed about refactoring coming after. I wonder if Claude Opus 5.5, which maybe is more acquainted with Haskell, would propose an alternative where a type class makes some of the calls cheaper (calls to a known function) via aggressive specialization (which is, sadly, fragile and only works in new GHCs). |
|
I think we should wait for the main reviewer @augustss for now. |
|
I'm not an export on I will deal with the |
|
I think it's okay now. |
|
thanks! before that, I'm gonna make sure CI actually fetches mhs before starting tests otherwise the actual testing is skipped |
7ea4b19 to
f303670
Compare
…step The commits of haskell/cabal#11392 now install MicroHs in the validate workflow, so that the MHS tests run in CI. The Haskell sources of every commit are unchanged, and so are the results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
I think it is better to keep things as they are with regards to There is a benefit in having an enumeration of all possible alternatives: the only moving part is a simple enum. Replacing this mechanism with type class or record of functions makes it significantly harder to debug: now you need to track what's exactly in that record (and any part of pipeline could have changed it) or how exactly the type class got resolved. If we were churning compilers left and right - maybe, but if we do it once a decade or even once a year, it's not worth it. |
Add a MicroHs backend to Cabal, Distribution.Simple.MHS, so that cabal-install can build simple packages with mhs: a library with exposed and other modules, and executables depending on it, in place or in the store. Cabal knows mhs as a program, whose version is that of mhs --numeric-version, and configure, build, install and register dispatch to the backend for the compiler flavour MHS. MicroHs compiles a library into a single package file, and finds installed packages through a search path of directories with an index of the modules they provide. Cabal maps its package databases onto this as follows (see Note [MicroHs package databases] in Distribution.Simple.MHS): * The global database is the default search path of mhs, reported by 'mhs -L'; its packages (base, MicroHs) are described by 'mhs -L<pkgfile>' and get <name>-<version> as their unit id, which is what the solver and 'configure' then refer to. * A database of Cabal is a directory of <unit-id>.conf registration files; the package file of a library stays in its build directory or, once installed, in its library directory. * A component is compiled with the package files of its dependency closure preloaded (-p) and the search path cleared (-a), so that it sees exactly the packages it depends on, and a library rebuilt in place is picked up by its dependents without re-registration. Executables are linked by mhs into <builddir>/<exe>/<exe>, where cabal-install expects them, so 'cabal run' and 'cabal list-bin' work. 'Setup configure' gains a --mhs flag, the package databases are monitored for changes, and 'cabal path' knows the compiler program. The help texts of the commands in doc/ are regenerated for --mhs and the mhs program. The modules that preprocessors generate, which Cabal puts in the build directory of a component, are on the module search path of mhs after the source directories, and the main module of an executable is looked for there first, as with GHC. For changes in the package databases, cabal-install monitors their directories, their packages subdirectories, and the registration and package files in them, which are rewritten in place when a package is registered or installed again. The headers that hsc2hs generates beside the modules are on the include path, for the C it generates with them. The dependencies of a component are taken from the index of configure, which build keeps up to date with the libraries it builds; the package databases are read again only when a dependency is missing from it. The extra-lib-dirs of a component are made absolute before mhs stores them in the package file of a library, as the programs linked against it may be built elsewhere. Executables are compressed with -z, as MicroCabal does, so that the mhs that cabal builds is the size of the one its Makefile builds. Every extension is described as supported, as MicroCabal effectively does by not checking them: mhs has the extensions it implements always on, CPP excepted, and ignores the LANGUAGE pragmas of the others. Describing only the extensions it implements made Cabal reject packages for extensions they merely declare, often for the GHC side of a conditional: Trustworthy (parsec), DeriveGeneric (pretty), UnboxedTuples (unordered-containers, vault), TypeFamilies (newtype, contravariant, assoc), or NoGeneralizedNewtypeDeriving, for which the solver fell back from time-1.15, which supports mhs, to time-1.12.2. A package that needs an extension mhs lacks fails when mhs meets the syntax. reexported-modules are accepted too: mhs sees all the modules of the packages it loads, which include the dependencies of the dependencies of a component, so the modules a package re-exports are visible to the components depending on it, as long as they are not renamed; buildLib rejects a library that renames one. The errors of the backend are defined in Distribution.Simple.MHS too. Tested with mhs 0.16.9.0 and 0.15.4.0: cabal build/run/list-bin of a library with a hidden module plus an executable, an up-to-date rebuild, a rebuild after editing the hidden module, and 'cabal install' of the executable through the store. The test MHS/Build of cabal-testsuite covers the first of these, MHS/Preprocess builds modules that hsc2hs generates, and MHS/ReexportRenamed checks the rejection; they are skipped when mhs is not installed. The validate workflow installs MicroHs from source on Linux, for the GHC of the release, so that they run in CI. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
The base of mhs is versioned like the compiler (base-0.16.9.0) and bundles
the modules of packages that are separate on Hackage: bytestring, text,
deepseq, stm, directory, process, hashable and integer-logarithms. A package
written for GHC depends on base-4.x and on those packages by name, so it
cannot be solved against what mhs provides: the only installed base is
0.16.9.0, and base is not reinstallable.
Describe the base of mhs the way MicroCabal does, with the versions from its
builtinPackages table ("totally fake; they are from GHC"): base at 4.19.1.0,
and the bundled packages as installed packages of their own, at the versions
MicroCabal gives them, depending on base, with the modules that are theirs
and no package file. The unit id of base stays base-0.16.9.0, which is also
the name of its package file, so the dependency closure of a component still
preloads the one base.pkg. A package file is now looked up under the unit id
first, then under the munged package id of a library built by Cabal, and a
package without library directories has nothing to load.
Only hashable gets a version of its own. MicroCabal has it at 1.0.0.0, "very
rudimentary", which makes no difference to it, as it does not check bounds.
But the Data.Hashable of mhs has the API of the 1.4 series
(Data.Hashable.Lifted, Hashable1 with an Eq1 superclass), and the packages
that depend on hashable require at least that: unordered-containers 1.4,
these 1.4.4. With 1.0.0.0, the solver built hashable-1.5.1.0 from Hackage for
them instead, which mhs cannot compile, so hashable is presented at 1.4.7.0.
A base that Cabal registered, built from the sources of MicroHs, is
presented the same way as the one that comes with mhs; its package file
keeps the name it was built under, which its registration records as its
compat package key, and is found by it. A library without
modules, which Cabal registers with its extra-lib-dirs as its library
directories, has no package file to load there; its linker options come
from its registration.
Tested with mhs 0.16.9.0: a library with 'base ^>=4.19, bytestring >=0.11'
and an executable with 'base >=4.14 && <5' solve, build and run, with only
base.pkg preloaded; the testsuite package has such bounds now.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
GHC includes cabal_macros.h with -optP-include. mhs passes on to cpphs only the -D and -I options it is given, so give it the macros of the header one by one, as MicroCabal does. Distribution.Simple.MHS turns every #define of the header that generateCabalMacrosHeader renders, which is unchanged, into a -D option: VERSION_<pkg> and MIN_VERSION_<pkg>(major1,major2,minor) for the package and its dependencies, the TOOL_ variants for the configured programs, and the CURRENT_ constants, the first definition of a macro winning as in the header. The autogen directory of the component is on the include path of cpphs too, so a module that includes cabal_macros.h itself finds it. Many packages choose code on these macros, transformers for instance its Semigroup instances, and without them took the branch for an ancient base. The test of mhs moves to PackageTests/MHS/Build, and PackageTests/MHS/Macros checks the macros; it is skipped unless mhs and its cpphs are installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
MicroCabal rewrites the dependencies of every package it builds with mhs: it adds ghc-compat, which holds modules that are part of base in GHC but not in mhs, such as GHC.Stack and Language.Haskell.TH.Syntax, and it uses the forks array-mhs and random-mhs instead of array and random, which mhs cannot compile. Packages written for GHC rely on both. Distribution.Simple.MHS.rewritePackageDescription does the same, and cabal-install applies it to all the packages it gives to the solver (rewriteSourcePackages, next to the rewriting for allow-newer), so that the solver chooses ghc-compat and the forks, and cabal-install builds them from Hackage like any other dependency. Nothing has to be installed for mhs beforehand, and offline, where they cannot be had, the packages are left as they are. ghc-compat is added at the root of every component, unless the component depends on it there already, with bounds of its own; a dependency under a condition, or in another component, is no reason to leave it out. A re-export that names the package of a module, array:Data.Array say, names the fork too, as Cabal resolves it by that name. See Note [MicroHs rewrites of packages]. Rewriting the packages before solving is enough thanks to a recent change of cabal-install, which calls Cabal in-library (#11703): it configures a package with the description of its plan, which is rewritten already, rather than having a Setup script read the .cabal file of the package again. Before that change, configure had to rewrite the package once more. Only a Custom setup script still reads the .cabal file, and mhs cannot compile one. As with MicroCabal, the version macros of a fork are also defined under the name of the package it forks: QuickCheck, which depends on random, tests MIN_VERSION_random(1,3,0) to define its RandomGen instance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
mhs compiles no C when it builds a library. It compiles the C sources of a library when it links a program that loads the package file of the library, from <name>-<version>/cbits beside the package file, which is where MicroCabal installs the c-sources of a library. Cabal ignored the C sources of libraries with a warning, and gave mhs no include directories of the dependencies of a component, so a program calling into the C code of a library failed to compile, as with time, which takes the time zone from C: fatal error: HsTime.h: No such file or directory buildLib now copies the c-sources of a library there, beside the package file in its build directory, and installLib copies them along with the package file. The headers are found as with GHC: the include-dirs that the registrations of the dependencies of a component list, which is where Cabal installs their install-includes for every compiler, are given to mhs with -I, which it passes on to the C compiler. See Note [MicroHs C files of libraries]. The cc-options of an executable are now passed to mhs, with -optc, for its own C sources. The headers beside the C sources go with them, as the sources include them relative to themselves, and the copies are what gets compiled. Two headers of the same name have to be the same header, as the C sources have to have distinct names, since mhs compiles them from one directory. The headers beside the C sources of the dependencies, such as those that hsc2hs generates for a #def, are on the include path too, for the C that mhs generates for their foreign imports. The linker options of a dependency without a package file to load, a library without modules say, are passed on from its registration, as -optl: into the package file of a library, so that they propagate, and to the link of a program. installLib removes what an earlier installation of the library left in its directory before copying, as mhs compiles every C file there. The test MHS/CSources builds a library with C sources, a private header beside them and an installed one, from a repository, into the store, and another one in place, and runs an executable calling both. The test MHS/Preprocess defines a C function with #def in the .hsc module of its library, which hsc2hs writes beside the module with a header, and calls it from the executable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
mhs has no package environments: it finds packages in the package directories of its search path, into which 'mhs -Q' installs them. With mhs, cabal install --lib crashed, as it asked about the package environments of a compiler other than GHC: Distribution.Simple.GHC.ImplInfo.getImplInfo only works for GHC, but found MHS It now installs the libraries, with the libraries they depend on that mhs does not find already, into a package directory of mhs with 'mhs -Q', and the C sources and headers of each beside it, where mhs looks for them. The directory is UserPackageDB, where MicroCabal installs packages, ~/.mcabal/mhs-<version> or $CABALDIR/mhs-<version>, unless another one is given with --package-env. After 'make minstall', ~/.mcabal/mhs-<version> is the package directory of mhs itself, so that mhs finds the libraries there as it finds those of MicroCabal. The package directory of the user that the configuration of mhs names, ~/.mcabal-user/mhs-<version>, would not do: MicroHs 0.16.9.0 does not search it, as it expands $VERSION in its configuration to its own directory instead of its version. cabal says whether mhs searches the directory or has to be given it with -a, as it has to when mhs is installed elsewhere; mhs names the directories of its search path as it reaches them, such as /path/to/bin/../mhs-0.16.9.0, so they are compared canonicalized. The directory of the C files of a library that 'install --lib' installs is named after its package file, whatever version the library is presented at, as mhs looks for it there, and the libraries installed are reported by that name too. The test MHS/InstallLib installs a library with a C source where MicroCabal installs and compiles a program using it with plain mhs; it sets CABALDIR to a directory of its own, as the home directory it sets is not honoured on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
f303670 to
f62e6be
Compare
| -- See Note [MicroHs rewrites of packages]. | ||
| rewritePackageDescription | ||
| :: (PackageName -> Bool) -> GenericPackageDescription -> GenericPackageDescription | ||
| rewritePackageDescription available gpd0 |
There was a problem hiding this comment.
Sorry for a late review, but I don't think enshrining these shenanigans in Cabal-the-library is an appropriate solution.
If packages need to use MicroHS-specific libraries, they can declare them explicitly under if impl(mhs). Otherwise hardcoding things like forkedPackages in Cabal-the-library will only cause grief as soon as their list changes (reducing or growing) or upstream packages make a new release. Swapping dependencies under the hood and subverting Cabal solver is a dangerous approach.
Instead of forking stuff and manipulating versions, MicroHS can provide a Hackage overlay. This is similar in spirit to HEAD.hackage, which provides a compatibility layer for GHC HEAD. Such approach would not require Cabal-the-library to maintain an up-to-date list of forks and what not.


What's achieved
lib:Cabalandcabal-installcan build, run, test and install packages with MicroHs,mhs, the way its own package manager,mcabal, does.AI disclosure: this was written largely by Claude Code (Fable 5.1 or Opus 5.5 depending on the weather outside). I did review it myself and it's a result of many iterations...
Size
The patch looks big but fear not: most of it is contained in the new
MHS.hsmodule and tests.cabal-installMHS.hsDistribution.Simple.MHS(MHS.hs)cabal-testsuitevalidate.yml)(Counted with
git diff --numstatagainstmasteratc433b30.)Notes:
The most annoying part and the one to focus your review on is
cabal-install. In an ideal world, we'd not need to touch it at all but as the Commit history (below) explains, concessions had to be made.Changes to Cabal outside
MHS.hsare trivial, which is a strong point.Of the lines added to
cabal-testsuite, 84 are in the 6 test scripts and 292 in the packages they build.22 of the 28 files in docs are the generated help texts of the commands, which gain
--mhsand themhsprogram.Commit history
Each commit ends with how many of the 91 packages of MicroHs's own list build, and run a program, with
cabalat that commit, replayed for every commit in the same way, with nothing installed for mhs besides itsbase(see Testing below and all results in a separate repo).cfced1dAdds themhsprogram and the backendDistribution.Simple.MHS, to which configure, build, install and register dispatch: it builds libraries into mhs package files and executables against them, compressed with-zasmcabaldoes, and installs and registers both, in place and in the store; the packages of mhs itself are described withmhs -L. Every extension is accepted, as mhs ignores the pragmas of those it lacks, and so arereexported-modulesthat keep their names, as mhs sees every module of the packages it loads. 3/91 build and run: withbaseat 0.16.9.0, the solver rejects most packages from Hackage, or falls back to ancient versions of them, until the next commit.cb3743ePresents thebaseof mhs at version 4.19.1.0, and the packages it bundles, such asbytestringandtext, as installed packages at the versions MicroCabal uses, excepthashable, at 1.4.7.0, the API that mhs implements, rather than MicroCabal's 1.0.0.0. 39/91 build and run.373d4b4Passes the macros ofcabal_macros.h, such asMIN_VERSION_base, to the preprocessor of mhs as-Doptions. 58/91 build and run.9338e00Rewrites, incabal-install, the packages that the solver sees, as MicroCabal does:ghc-compat, which has the modules that are part ofbasewith GHC, becomes a dependency of every component, andarrayandrandombecome their forksarray-mhsandrandom-mhs, so thatcabalplans and builds all three from Hackage, and mhs needs nothing installed besidesbase. Rewriting what the solver sees is enough becausecabal-installbuilds packages in-library (#11703): it configures each package with the description in its plan, which is rewritten already, rather than running aSetupscript that would read the original.cabalfile again. 88/91 build and run.b743e6bPuts the C sources of a library, and the headers beside them, beside its package file, where mhs compiles them when it links a program, and gives mhs the include directories of the dependencies with-I. 91/91 build and run.f62e6becabal install --libinstalls libraries and their dependencies withmhs -Qinto the directory wheremcabalinstalls packages, which is the package directory of mhs itself aftermake minstall, or into the one given with--package-env, so that plainmhsfinds them, rather than writing a GHC environment file. 91/91 build and run.Testing: 3 parts
Part 1:
cabal-testsuitecabal-testsuite/PackageTests/MHS/Buildbuilds and runs a library with a hidden module and an executable using it, with the bounds of a package from Hackage, and is skipped whenmhsis not on the PATH.MHS/Macroschecks the version macros and is skipped unlesscpphs, which mhs runs for CPP, is on the PATH too.MHS/CSourceschecks the C sources of a library built in place and of one installed in the store from a local repository, each with a private header beside them, andMHS/InstallLibinstalls a library withcabal install --liband compiles a program against it with mhs alone.MHS/Preprocessbuilds a library module and the main module of an executable from.hscfiles, which hsc2hs turns into Haskell in the build directories, with a C function the library defines with#def, and is skipped without hsc2hs;MHS/ReexportRenamedchecks that a library re-exporting a module under another name, which mhs cannot provide, is rejected. The validate workflow installs MicroHs from source on Linux for the GHC of the release, in about a minute on a cache miss, so that the tests run in CI; elsewhere they are skipped. To run them locally:Part 2: MicroHs's own package list
MicroHs's
Makefile.packagesis the list of 91 Hackage packages itshackage-ciworkflow installs withmcabalon every push. https://github.com/ulysses4ever/cabal-mhs-testsuite builds them withcabaland mhs instead, one at a time and in the same order, each into a store shared by all the steps, with this PR's commits applied; its CI also runscabal-testsuite/PackageTests/MHS. Every step also runs a small executable depending on the package. As mhs compiles C sources when it links a program, even the executable that does nothing compiles the C sources of the package and its dependencies. Fortime,splitmix,random,QuickCheck,hspec,terminfoandbinary, the executable calls into the package: QuickCheck passes 100 tests, hspec reports1 example, 0 failures, andterminfolinks with the curses library its configure script found. Wheremcabaldiffers fromcabal, the replay does whatmcabaldoes, and its README says how: the--gitoverrides becomesource-repository-packagestanzas, and sincemcabalignores version bounds, a few are lifted inproject.extra, each with its reason. mhs has nothing installed besidesbase;cabalbuildsghc-compatand the forks itself.All the counts in the list of commits are for compiling each package as it is published, with no settings of its own: the project file sets no flags and no compiler options for any package of the list. It only chooses versions and sources: the
index-state, the git repositories of MicroHs's Makefile, the three bounds thatproject.extralifts and its one constraint, and a copy ofhspec-corewith its missing dependency added. The tiny executable of each step, which is not one of the packages, is linked with--no-as-neededon Linux.The version macros take the count from 39 to 58: without them, transformers, which defines its
Semigroupinstances under#if MIN_VERSION_base(4,9,0), blocked 26 packages, and prettyprinter stopped at its own#error "MIN_VERSION_base macro not defined!". The rewrite takes it from 58 to 88:arrayfrom Hackage, which mhs cannot compile, blocked 17 of those 30 packages untilarray-mhsreplaced it, and withoutghc-compat, exceptions and call-stack failed onGHC.Stack, and time onLanguage.Haskell.TH.Syntax. The last three, time, binary and these, through binary, needed the headers of C sources, such asHsTime.h, which the commit for C sources puts on the include path.One package needs a fix to build with
cabal: hspec-core importsasyncwithout depending on it, as it compiles its vendored copy of async only for GHC. That works withmcabalbecause mhs sees every package thatmcabalhas installed. Cabal gives mhs only the declared dependencies, so the replay addsif impl(mhs)/build-depends: asyncto a copy of hspec-core, which is the fix that belongs upstream; without it, hspec-core and hspec above it fail.Running the programs found problems outside
cabal, in mhs and in some packages, whichmcabalhas too;INVENTORY.mdlists them with reproductions.Part 3: Building MicroHs itself (after patching it up)
MicroHs builds its
basewithmcabal buildinlib. We allowcabalbuild it withmhstoo, and the compiler fromMicroHs.cabal, once four problems of those.cabalfiles thatmcabaldoes not mind are fixed:library basehas to belibrary,Data.Floatis listed twice,basehave no source,MicroHs.caballists for every compiler seven modules that only GHC needs.microhs-cabal-fixes.patchfixes them, and MicroHs still builds with GHC with it.basealso needsallow-boot-library-installs: True, as with any boot library.The
basethatcabalbuilds has the same modules, exports and definitions as the onemcabalbuilds.cabalis no slower than the Makefile andmcabal, and, as it compresses programs asmcabaldoes, it builds anmhsof exactly the size of the Makefile's: the same machine code, and the same combinator code but for how 52 source locations ofbaseare spelled. The testsuite's README has the times and sizes. An installation of mhs whosebaseand compilercabalbuilt passes the tests ofcabal-testsuite/PackageTests/MHSand builds and runs the whole package list, and the testsuite's CI builds both on every run (CI run).cabal install exe:mhsdoes not give a working mhs, though: mhs looks for its packages and runtime only beside its binary, in the layout of its own installer.INVENTORY.mdlists every issue or wrinkle this work found outsidecabal: in mhs, the MicroHs repository, MicroCabal, and some packages on Hackage, with reproductions.Not covered yet
mhs-optionsin.cabalfiles,cabal repl, Backpack, foreign libraries, profiling and shared ways.hspec-coreshould depend onasyncfor mhs, mhs could publish the versions at which its bundled packages should be presented, mhs could pass the libraries of packages after the C files, declare the C functions a program imports and expand$VERSIONin mhs.conf to its version; MicroCabal could move to time 1.16.0.1, which fixed the time zone that time 1.15 gets wrong under mhs.Checklist
significance: significantin the changelog file.QA Notes
A smoke test, and how to extend it in the direction you want to check.
Set up. Install MicroHs v0.16.9.0 from source, which also installs
cpphs, the preprocessor that mhs runs for CPP and that the nixpkgs package lacks, and buildcabalfrom this branch:Smoke test. A library, and an executable using it:
Expected:
Build profile: -w mhs-0.16.9.0 -O1, andghc-compatbuilt from Hackage first, as every package depends on it with mhs;base ^>=4.19andbytestringresolve to what thebaseof mhs provides. With-v2, themhscommands show the library compiled into a package file, and the executable compiled with that file andbasepreloaded (-p) and the search path of mhs cleared (-a).Extend it. The tests in
cabal-testsuite/PackageTests/MHSdo most of these:randomto the library'sbuild-depends, andcabal-mhs buildbuilds the forkrandom-mhsinstead.{-# LANGUAGE CPP #-}toMyLib, and choose code with#if MIN_VERSION_base(4,19,0)(MHS/Macros).c-sources: cbits/twice.c,include-dirs: includeandinstall-includes: twice.h, andMyLibaforeign import ccall "twice.h twice" twice :: CInt -> CInt; mhs compiles the C file when it links the executable (MHS/CSources).cabal-mhs install exe:mhs-demobuilds the package in the store and installs the executable.cabal-mhs install --libinstalls the library wheremcabalinstalls packages, into~/.mcabal/mhs-0.16.9.0, whichmake minstallmade the package directory of mhs itself, so thatmhs Prog.hscompiles a program that importsMyLib(MHS/InstallLib).🤖 Generated with Claude Code
https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj